#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
Simplified BSD License (BSD)
Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 17-Nov-09 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;

using Thn;
#endregion

namespace Thn.Interface.Vcl
{
    /// <summary>
    /// Base class for multi-page controls that show page header as tabs.
    /// </summary>
    [ToolboxItem(false)]
#if DEBUG
    public
#else
    public
#endif
    abstract class TabControlBase : ControlBase, IControlContainerHost
    {
        #region Fields
        /// <summary>
        /// List of tabs.
        /// </summary>
        protected ControlContainerList mPages = null;
        /// <summary>
        /// Currently selected tab.
        /// </summary>
        protected ControlContainer mCurrent = null;
        /// <summary>
        /// Region of currently shown page.
        /// </summary>
        protected Rectangle rPageRegion = Rectangle.Empty;
        /// <summary>
        /// Region of tab headers.
        /// </summary>
        protected Rectangle rTabRegion = Rectangle.Empty;

        /// <summary>
        /// Size of the tab region.
        /// </summary>
        protected int mTabRegionSize = 25;
        /// <summary>
        /// Horizontal size of a tab.
        /// </summary>
        protected int mTabWidth = 75;
        /// <summary>
        /// Vertical size of a tab.
        /// </summary>
        protected int mTabHeight = 22;
        /// <summary>
        /// The padding between tabs.
        /// </summary>
        protected int mTabPadding = 1;
        /// <summary>
        /// Arrangement of tabs.
        /// </summary>
        protected TabLayout mLayout = TabLayout.Top;
        /// <summary>
        /// Index of currently selected tab.
        /// </summary>
        protected int mSelectedIndex = -1;
        #endregion

        #region Properties

        #region Render Context
        private TabControlRenderContext mRenderContext = new TabControlRenderContext();
        /// <summary>
        /// Gets reference to current renderation context.
        /// </summary>
        protected TabControlRenderContext RenderContext
        {
            get { return mRenderContext; }
        }
        #endregion

        #endregion

        #region Show by index
        /// <summary>
        /// Show a page in list by its location index.
        /// </summary>
        public void ShowPage(int index)
        {
            this.SuspendLayout();

            //turn off current page
            if (mCurrent != null)
            {
                mCurrent.Visible = false;
            }
            #region Defensive tracing
            else
            {
                //no current page yet
            }
            #endregion

            this.Controls.Clear();

            //turn on the new page
            if ((index > -1) && (index < mPages.Count))
            {
                ControlContainer con = mPages[index];
                this.Controls.Add(con);
                con.SetBounds(rPageRegion.Left, rPageRegion.Top, rPageRegion.Width, rPageRegion.Height);
                con.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;
                con.Visible = true;
                mCurrent = con;
            }
            #region Defensive tracing
            else
            {
                //invalid index, show nothing
            }
            #endregion
        }
        #endregion

        #region On Get Render Context
        /// <summary>
        /// Get reference to the renderation context which is used by <see cref="IControlRenderer"/>.
        /// </summary>
        protected override RenderContext OnGetRenderContext(Graphics canvas)
        {
            return mRenderContext;
        }
        #endregion

        #region On Calculate Layout
        /// <summary>
        /// Perform layout calculations neccessary for this control.
        /// </summary>
        protected override void OnCalculateLayout()
        {
            Rectangle rControl = this.ClientRectangle;
            Rectangle r = rControl;
            rTabRegion = new Rectangle(r.Left, r.Top, r.Width, mTabRegionSize);
            r = rTabRegion;
            rPageRegion = new Rectangle(r.Left, r.Bottom, r.Width, rControl.Height - mTabRegionSize);

            //assign values to render context
            mRenderContext.TabsRegion = rTabRegion;
            mRenderContext.PageRegion = rPageRegion;

            //calculate tabs
            mRenderContext.Tabs = OnCalculateTabsLayout();

            //adjust current page
            if (mCurrent != null) mCurrent.SetBounds(rPageRegion.Left, rPageRegion.Top, rPageRegion.Width, rPageRegion.Height);
            #region Defensive tracing
            else
            {
                //no page selected
            }
            #endregion
        }
        #endregion

        #region On Calculate Tabs Layout()
        /// <summary>
        /// Calculate the layout of tabs.
        /// </summary>
        protected virtual TabRenderContext[] OnCalculateTabsLayout()
        {
            TabRenderContext[] result = null;

            if (mPages.Count > 0)
            {
                #region Has Pages

                result = new TabRenderContext[mPages.Count];
                ControlContainer page = null;
                TabRenderContext tab = null;
                int x = 0;
                int y = 0;
                switch (mLayout)
                {
                    #region Left
                    case TabLayout.Left:
                        throw new NotSupportedException(string.Format("Does not support layout value of {0} yet", mLayout));
                        break;
                    #endregion

                    #region Right
                    case TabLayout.Right:
                        throw new NotSupportedException(string.Format("Does not support layout value of {0} yet", mLayout));
                        break;
                    #endregion

                    #region Top
                    case TabLayout.Top:
                        y = mRenderContext.TabsRegion.Bottom - mTabHeight;
                        x = mTabPadding;
                        for (int i = 0; i < mPages.Count; i++)
                        {
                            page = mPages[i];
                            tab = new TabRenderContext();
                            tab.Bounds = new Rectangle(x, y, mTabWidth, mTabHeight);
                            tab.Container = page;
                            tab.TextRegion = tab.Bounds;
                            result[i] = tab;

                            //next iteration
                            x += mTabWidth + mTabPadding;
                        }
                        break;
                    #endregion

                    #region Bottom
                    case TabLayout.Bottom:
                        throw new NotSupportedException(string.Format("Does not support layout value of {0} yet", mLayout));
                        break;
                    #endregion

                    #region Unsupported
                    default:
                        throw new NotSupportedException(string.Format("Does not support layout value of {0} yet", mLayout));
                        break;
                    #endregion
                }

                #endregion
            }
            else
            {
                #region Has No Pages

                result = new TabRenderContext[0];

                #endregion
            }

            return result;
        }
        #endregion

        #region On Tab Added
        /// <summary>
        /// This method is raised when a new tab has been added.
        /// </summary>
        protected virtual void OnTabAdded(ControlContainer tab)
        {
            InvalidateLayout();
        }
        #endregion

        #region On Tab Removed
        /// <summary>
        /// This method is raised when a tab has been removed.
        /// </summary>
        protected virtual void OnTabRemoved(ControlContainer tab)
        {
            InvalidateLayout();
        }
        #endregion

        #region On Tab Updated
        /// <summary>
        /// This method is raised when content of a tab has been updated.
        /// </summary>
        protected virtual void OnTabUpdated(ControlContainer tab)
        {
            InvalidateLayout();
        }
        #endregion

        #region On Tabs Cleared
        /// <summary>
        /// This method is raised when all tabs have been removed.
        /// </summary>
        protected virtual void OnTabsCleared()
        {
            InvalidateLayout();
        }
        #endregion

        #region On Tab Selected By Mouse
        /// <summary>
        /// This method is raised when user clicks on a specific tab.
        /// </summary>
        protected abstract void OnTabSelectedByMouse(int tabIndex);
        #endregion

        #region Mouse Events

        #region On Mouse Down
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            int tabIdx = -1;
            HitTestRegion hit = HitTest(e.X, e.Y, ref tabIdx);
            if (hit == HitTestRegion.Tab)
            {
                #region A specific tab is hit

                if (tabIdx != mSelectedIndex)
                {
                    OnTabSelectedByMouse(tabIdx);
                }
                #region Defensive tracing
                else
                {
                    //selected the same tab, not interested
                }
                #endregion

                #endregion
            }
            else
            {
                #region Others

                //not interested

                #endregion
            }
        }
        #endregion

        #endregion

        #region Hit Test

        #region Hit Test Region
        /// <summary>
        /// Possible region for mouse hit-testing.
        /// </summary>
        public enum HitTestRegion
        {
            /// <summary>
            /// Not any important region.
            /// </summary>
            None,
            /// <summary>
            /// Within a specific tab.
            /// </summary>
            Tab,
            /// <summary>
            /// Within tab header region but not any specific tab.
            /// </summary>
            TabArea,
            /// <summary>
            /// Within the close button of a tab.
            /// </summary>
            TabCloseButton,
            /// <summary>
            /// Within the current page region.
            /// </summary>
            Page
        }
        #endregion

        #region Hit Test
        /// <summary>
        /// Perform mouse hit-testing operation.
        /// </summary>
        /// <param name="x">X-axis coordinate of the point to be hit-tested.</param>
        /// <param name="y">Y-axis coordinate of the point to be hit-tested.</param>
        /// <param name="tabIndex">Index of the tab being hit.</param>
        public HitTestRegion HitTest(int x, int y, ref int tabIndex)
        {
            HitTestRegion result = HitTestRegion.None;
            tabIndex = -1;

            if (rTabRegion.Contains(x, y))
            {
                #region Within Tab Region

                result = HitTestRegion.TabArea;
                int tx = x + mRenderContext.TabOffsetX;
                int ty = y + mRenderContext.TabOffsetY;

                //search for the tab being hit
                TabRenderContext[] tabs = mRenderContext.Tabs;
                TabRenderContext tab = null;
                for (int i = 0; i < tabs.Length; i++)
                {
                    tab = tabs[i];
                    if (tab.Bounds.Contains(tx, ty))
                    {
                        //found the tab
                        tabIndex = i;
                        result = HitTestRegion.Tab;

                        //check if the close button is being hit
                        if (tab.ShowCloseButton && tab.CloseButtonRegion.Contains(tx, ty))
                        {
                            result = HitTestRegion.TabCloseButton;
                        }
                        #region Defensive tracing
                        else
                        {
                            //not the close button
                        }
                        #endregion

                        //break out of loop
                        break;
                    }
                    #region Defensive tracing
                    else
                    {
                        //not the tab we're looking for, move on
                    }
                    #endregion
                }

                #endregion
            }
            else if (rPageRegion.Contains(x, y))
            {
                #region Within Page Region

                result = HitTestRegion.Page;

                #endregion
            }
            else result = HitTestRegion.None;

            return result;
        }
        #endregion

        #endregion

        #region IControlContainerHost Members

        #region Notify Child Added
        /// <summary>
        /// This method is raised by the list to notify that it has received a new child.
        /// </summary>
        /// <param name="sender">The list that raises this notification.</param>
        /// <param name="child">The newly added child.</param>
        void IControlContainerHost.NotifyChildAdded(ControlContainerList sender, ControlContainer child)
        {
            OnTabAdded(child);
        }
        #endregion

        #region Notify Child Removed
        /// <summary>
        /// This method is raised by the list to notify that it has removed a child.
        /// </summary>
        /// <param name="sender">The list that raises this notification.</param>
        /// <param name="child">The child that was removed.</param>
        void IControlContainerHost.NotifyChildRemoved(ControlContainerList sender, ControlContainer child)
        {
            OnTabRemoved(child);
        }
        #endregion

        #region Notify Child Updated
        /// <summary>
        /// This method is raised by the list to notify that a child has been updated.
        /// </summary>
        /// <param name="sender">The list that raises this notification.</param>
        /// <param name="child">The newly updated child.</param>
        void IControlContainerHost.NotifyChildUpdated(ControlContainerList sender, ControlContainer child)
        {
            OnTabUpdated(child);
        }
        #endregion

        #region Notify Children Cleared
        /// <summary>
        /// This method is raised by the list to notify that it has removed all children.
        /// </summary>
        /// <param name="sender">The list that raises this notification.</param>
        void IControlContainerHost.NotifyChildrenCleared(ControlContainerList sender)
        {
            OnTabsCleared();
        }
        #endregion

        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        public TabControlBase()
        { }
        #endregion
    }    
}
